#pragma once
#ifndef TM_GPU_CIRATEFI_H_
#define TM_GPU_CIRATEFI_H_

#include <cuda_runtime.h>
#include <helper_cuda.h>
#include <cv.h>

namespace recognition {
namespace gpu {

/**
 * Implements CIRATEFI: Grayscale Template-Matching Invariant to Rotation,
 * Scale, Translation, Brightness and Contrast by Hae Yong Kim and Sidnei
 * Alves de Araujo.
 *
 * Contains both GPU and CPU implementations. Simple usage:
 *  Mat image_	= cv::imread("image.png", CV_LOAD_IMAGE_GRAYSCALE);
 *  Mat template_ = cv::imread("template.png", CV_LOAD_IMAGE_GRAYSCALE);
 *  ... // make odd dimensions and smooth image, template
 *
 *	CiratefiBoundaries c_bounds;
 *	c_bounds.s = BoundsWithStep<double>(0.1, 0.5, 2);
 *	c_bounds.r = BoundsWithStep<int>(2, 0, min(template_.cols / 2, template_.rows / 2) - c_bounds.r.step);
 *	c_bounds.phi = BoundsWithStep<double>(2 * M_PI / 36, 0, 2 * M_PI);
 *	c_bounds.angle_incr = 2;
 *	c_bounds.line_incr = 2;
 *
 *	recognition::gpu::Ciratefi ciratefi = recognition::gpu::Ciratefi::Builder()
 *		.I1(template_)
 *		.I2(image_)
 *		.BC_thresholds(BCThresholds(0.1, 1))
 *		.thresholds(CiratefiThresholds(0.8, 0.8, 0.5))
 *		.bounds(c_bounds)
 *		.maximum_shared_memory(max_shared_memory)
 *		.build();
 */
class Ciratefi {
public:
	class Builder : public IBuilder<Ciratefi> {
	public:
		Builder& I1(cv::Mat& image) {
			this->I1_ = image;
			return *this;
		}

		Builder& I2(cv::Mat& image) {
			this->I2_ = image;
			return *this;
		}

		Builder& BC_thresholds(BCThresholds thresholds) {
			this->bc_thresholds_ = thresholds;
			return *this;
		}

		Builder& thresholds(CiratefiThresholds thresholds) {
			this->thresholds_ = thresholds;
			return *this;
		}

		Builder& bounds(CiratefiBoundaries bounds) {
			this->bounds_ = bounds;
			return *this;
		}

		Builder& maximum_shared_memory(int maximum_shared_memory) {
			this->maximum_shared_memory_ = maximum_shared_memory;
			return *this;
		}

		Ciratefi build() {
			return Ciratefi(this);
		}

	private:
		cv::Mat I1_, I2_;
		BCThresholds bc_thresholds_;
		CiratefiThresholds thresholds_;
		CiratefiBoundaries bounds_;
		int maximum_shared_memory_;

		friend class Ciratefi;
	};

	static const log4cxx::LoggerPtr logger;
	static const log4cxx::LoggerPtr output;

	static std::vector<Point2D> MidpointLine(int x0, int y_0, int x1, int y_1, int line_incr);
	static std::vector<Point2D> MidpointCircle(int radius, int angle_incr);
	static void CirclePoints(int x, int y, std::vector<Point2D>& circle);

	~Ciratefi() {
		if (Q_ != NULL) {
			free(Q_);
			Q_ = NULL;
		}

		if (A_expanded_and_alligned_by_block_size_.elements != NULL) {
			checkCudaErrors(cudaFreeHost(A_expanded_and_alligned_by_block_size_.elements));
			A_expanded_and_alligned_by_block_size_.elements = NULL;
		}
		set_d_R_A(NULL);
		set_d_A_elements(NULL);
		set_d_A(NULL);
		set_d_C_A_elements(NULL);
		set_d_C_A(NULL);
	}

	// Runs CIRATEFI on GPU
	CiratefiResult RunGpu();
	// Runs CIRATEFI on CPU
	CiratefiResult RunCpu();

	// Creates matrix C_Q.
	//
	// \return	Mat(n, l)
	cv::Mat C_Q();

	// Creates matrix C_A.
	//
	// \return	Mat(I2_.width, I2_.height, l)
	cv::Mat C_A();
	// Creates array of I2_.width * I2_.height * l of double on device, which
	// stores matrix C_A and might be accessed by \ref ::d_C_A().
	void _cu_C_A();
	// Creates matrix C_A on host from array \ref ::d_C_A() on device.
	//
	// \return	Mat(I2_.width, I2_.height, l) C_A
	cv::Mat dev_to_host_C_A();

	// Evaluates set of first grade candidates.
	//
	// \param CQ	- Mat(n, l) C_Q
	// \param CA	- Mat(I2_.width, I2_.height, l) C_A
	// \return	set of first grade candidates
	std::vector<FirstGradePoint> CisCorr_AQ(cv::Mat& CQ, cv::Mat& CA);
	// Evaluates set of first grade candidates. It uses array on device of size
	// I2_.width * I2_.height * l as matrix C_A, which might be accessed by
	// \ref ::d_C_A().
	//
	// \param CQ	- Mat(n, l) C_Q
	// \return	set of first grade candidates
	std::vector<FirstGradePoint> _cu_CisCorr_AQ(cv::Mat& CQ);

	// Creates matrix R_Q.
	//
	// \return	Mat(1, m)
	cv::Mat R_Q();

	// Creates matrix R_A.
	//
	// \param fgps	- set of first grade candidates
	// \return Mat(I2_.width, I2_.height, m)
	cv::Mat R_A(std::vector<FirstGradePoint>& fgps);
	// Creates array of I2_.width * I2_.height * m of double on device, which
	// stores matrix R_A and might be accessed by \ref ::d_R_A().
	//
	// \param fgps	- set of first grade candidates
	void _cu_R_A(std::vector<FirstGradePoint>& fgps);
	// Creates matrix R_A on host from array \ref ::d_R_A() on device.
	//
	// \param fgps	- set of first grade candidates
	// \return	Mat(I2_.width, I2_.height, m) R_A
	cv::Mat dev_to_host_R_A(std::vector<FirstGradePoint>& fgps);

	// Evaluates set of second grade candidates.
	//
	// \param RQ	- Mat(1, m) R_Q
	// \param RA	- Mat(I2_.width, I2_.height, m) R_A
	// \param fgps	- set of first grade candidates
	// \return	set of first grade candidates
	std::vector<SecondGradePoint> RasCorr_AQ(cv::Mat& RQ, cv::Mat& RA, std::vector<FirstGradePoint>& fgps);
	// Evaluates set of second grade candidates. It uses array on device of size
	// I2_.width * I2_.height * m as matrix R_A, which might be accessed by
	// \ref ::d_R_A().
	//
	// \param RQ	- Mat(1, m) R_Q
	// \param fgps	- set of first grade candidates
	// \return	set of second grade candidates
	std::vector<SecondGradePoint> _cu_RasCorr_AQ(cv::Mat& RQ, std::vector<FirstGradePoint>& fgps);

	// Evaluates Tefi.
	//
	// \param sgps	- set of second grade candidates
	// \return	result of CIRATEFI
	CiratefiResult Tefi(std::vector<SecondGradePoint>& spgs);
	// Evaluates Tefi.
	//
	// \param sgps	- set of second grade candidates
	// \return	result of CIRATEFI
	CiratefiResult _cu_Tefi(std::vector<SecondGradePoint>& sgps);

	// Caches all circle points, necessary to run CIRATEFI.
	void CreateCircles();
	// Caches all line points, necessary to run CIRATEFI.
	void CreateLines();

	// Evaluates mean grayscale value of pixels on circle.
	//
	// \param I			- image
	// \param center	- center of circle
	// \param radius	- radius of circle
	// \return	mean value
	double Cis(cv::Mat& I, Point2D center, int radius);
	// Evaluates mean grayscale value of pixels on segment.
	//
	// \param I			- image
	// \param start		- starting point of segment
	// \param radius	- radius in polar coordinates of segment's end point
	// \param angle		- angle in polar coordinates of segment's end point
	// \return	mean value
	double Ras(cv::Mat& I, Point2D start, int radius, double angle);

	// Evaluates correlation between \p x and \p y.
	//
	// \param x			- vector x
	// \param y			- vector y
	// \param npoints	- size of \p x and \p y
	// \return	correlation
	double corr(double* x, double* y, int npoints);

	// Fields accessors
	cv::Mat& I1() { return I1_; }
	cv::Mat& I2() { return I2_; }
	BCThresholds bc_thresholds() const { return bc_thresholds_; }
	CiratefiThresholds thresholds() const { return thresholds_; }
	CiratefiBoundaries bounds() const { return bounds_; }

	int n() const { return n_; }
	int l() const { return l_; }
	int m() const { return m_; }

	double out_of_boundaries_value() const { return out_of_boundaries_value_; }
	void set_out_of_boundaries_value(double val) { out_of_boundaries_value_ = val; }

private:
	Ciratefi(Builder* builder) {
		this->I1_ = builder->I1_;
		this->I2_ = builder->I2_;
		this->bc_thresholds_ = builder->bc_thresholds_;
		this->thresholds_ = builder->thresholds_;
		this->bounds_ = builder->bounds_;
		this->maximum_shared_memory_ = builder->maximum_shared_memory_;
//		this->maximum_shared_memory_ = 0;	// for debug purpose

		make_images_continuous();
		init();
	}

	void make_images_continuous();

	void init() {
		// Adjust boundaries
		if (bounds_.s.step == 0) bounds_.s.step = 0.1;
		if (bounds_.r.step == 0) bounds_.r.step = 1;
		if (bounds_.phi.step == 0) bounds_.phi.step = 2 * M_PI;
		bounds_.r.min = (bounds_.r.min / bounds_.r.step) * bounds_.r.step;
		bounds_.r.max = (bounds_.r.max / bounds_.r.step) * bounds_.r.step;

		this->n_ = (bounds_.s.max - bounds_.s.min) / bounds_.s.step + 1;
		this->l_ = (bounds_.r.max - bounds_.r.min) / bounds_.r.step + 1;
		this->m_ = (bounds_.phi.max - bounds_.phi.min) / bounds_.phi.step + 1;
		this->max_lambda_ = bounds_.r.max * bounds_.s.max;

		init_offset();
		set_out_of_boundaries_value(100.0);
		A_expanded_and_alligned_by_block_size_ = Matrix2d();

		circle_radius_with_max_points_ = 0;
		max_num_of_circle_points_ = 0;
		total_num_of_circle_points_ = 0;
		max_num_of_line_points_ = 0;

		init_host_pointers();
		init_device_pointers();
	}

	int RadiusToIdx(int radius) {
		return recognition::RadiusToIdx(radius, bounds_);
	}

	int IdxToRadius(int idx) {
		return recognition::IdxToRadius(idx, bounds_);
	}

	int AngleToIdx(double angle) {
		return recognition::AngleToIdx(angle, bounds_);
	}

	double IdxToAngle(int idx) {
		return recognition::IdxToAngle(idx, bounds_);
	}

	int ScaleToIdx(double scale) {
		return recognition::ScaleToIdx(scale, bounds_);
	}

	double IdxToScale(int idx) {
		return recognition::IdxToScale(idx, bounds_);
	}

	Matrix2d* d_A() { return d_A_; }
	void set_d_A(Matrix2d* val) {
		if (d_A_ != NULL)
			checkCudaErrors(cudaFree(d_A_));
		d_A_ = val;
	}
	double* d_A_elements() { return d_A_elements_; }
	void set_d_A_elements(double* val) {
		if (d_A_elements_ != NULL)
			checkCudaErrors(cudaFree(d_A_elements_));
		d_A_elements_ = val;
	}
	Matrix2d* d_C_A() { return d_C_A_; }
	void set_d_C_A(Matrix2d* val) {
		if (d_C_A_ != NULL)
			checkCudaErrors(cudaFree(d_C_A_));
		d_C_A_ = val;
	}
	double* d_C_A_elements() { return d_C_A_elements_; }
	void set_d_C_A_elements(double* val) {
		if (d_C_A_elements_ != NULL)
			checkCudaErrors(cudaFree(d_C_A_elements_));
		d_C_A_elements_ = val;
	}
	double* d_R_A() { return d_R_A_; }
	void set_d_R_A(double* val) {
		if (d_R_A_ != NULL)
			checkCudaErrors(cudaFree(d_R_A_));
		d_R_A_ = val;
	}

	void init_offset() {
		// Prepare image with boundaries
		int w1 = I1_.cols * bounds_.s.max, h1 = I1_.rows * bounds_.s.max;

		// Set offset (boundaries)
		offset_ = sqrt(pow(w1 / 2 + 1, 2) + pow(h1 / 2 + 1, 2));
	}

	void init_host_pointers() {
		Q_					= NULL;
	}

	void init_device_pointers() {
		d_A_				= NULL;
		d_A_elements_		= NULL;
		d_C_A_				= NULL;
		d_C_A_elements_		= NULL;
		d_R_A_				= NULL;
	}

	double* Q();
	cv::Mat& A_expanded();
	Matrix2d A_expanded_and_alligned_by_block_size();

	void _cu_C_A__shared__();
	void _cu_C_A__global__();
	void _cu_R_A__global__(std::vector<FirstGradePoint>& fgps);

	double GetDevMemoryUsedMb() {
		return recognition::GetDevMemoryUsedMb();
	}

	void ReportDevMemoryUsage(const char* func_name, double dev_mem_used_start) {
		recognition::ReportDevMemoryUsage(logger, func_name, dev_mem_used_start);
	}

	void ReportMethodExecutionTime(const char* func_name, double time) {
		recognition::ReportMethodExecutionTime(logger, func_name, time);
	}

	void ReportKernelExecutionTime(const char* func_name, double time) {
		recognition::ReportKernelExecutionTime(logger, func_name, time);
	}

	void ReportCiratefiResult(CiratefiResult res, double max_corr) {
		recognition::ReportCiratefiResult(logger, bounds_, res, max_corr);
	}

	// Input parameters
	cv::Mat I1_, I2_;
	BCThresholds bc_thresholds_;
	CiratefiThresholds thresholds_;
	CiratefiBoundaries bounds_;
	int maximum_shared_memory_;
	// Values pre-calculated in constructor
	int n_, l_, m_;
	int max_lambda_;
	// Different cached numbers
	int max_num_of_circle_points_;
	int max_num_of_line_points_;
	int circle_radius_with_max_points_;
	int total_num_of_circle_points_;
	// Padding to ease border case evaluations
	int offset_;
	double out_of_boundaries_value_;
	// Image with padding for CPU
	cv::Mat A_expanded_;
	// Image with padding for GPU
	Matrix2d A_expanded_and_alligned_by_block_size_;

	double* Q_;

	Matrix2d* d_A_;
	double* d_A_elements_;
	Matrix2d* d_C_A_;
	double* d_C_A_elements_;
	double* d_R_A_;

	// Cache of circles' points
	std::vector<std::vector<Point2D> > circles_;
	// Cache of segments' points
	std::vector<std::vector<std::vector<Point2D> > > lines_;
};

}	// namespace gpu
}	// namespace recognition

#endif	// TM_GPU_CIRATEFI_H_
